﻿/*
 * XYQuadrantTransform
 * 
 * initial implementation : 2.12.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Petra.Gis.Geometry.Support;



namespace Petra.Gis.ReferenceSystemServices.Transformations
{

  /// <summary>
  /// provides simple quadrant transform between 2 ProjectedCoordinateSystems
  /// accepts 3 parameters 'SwapXY', 'FlipX', 'FlipY' of bool type
  /// SwapXY, if true exchange x and y value of coordinate
  /// FlipX, if true x = x * (-1)
  /// FlipY, if true y = y * (-1)
  /// </summary>
  [DataContract]
  public class XYQuadrantTransform : InversibleParametricTransform
  {


    protected bool _SwapXY = false;
    protected bool _FlipX = false;
    protected bool _FlipY = false;


    #region constructors

    /// <summary>
    /// xaml constr.
    /// </summary>
    public XYQuadrantTransform() 
      :base()
    {
      Metadata = new Metadata("X, Y Quadrant transform", "none", -1, "", "", "provides transformation between exactly same Coordinate systems with different x,y axis order and / or direction");
    }





    public XYQuadrantTransform(CoordinateSystem sourceCS, CoordinateSystem targetCS, TransformParameters parameters)
      : this(sourceCS, targetCS, false, parameters) { }





    public XYQuadrantTransform(CoordinateSystem sourceCS, 
                                      CoordinateSystem targetCS, 
                                      bool createInverse,
                                      TransformParameters parameters
                                      )
      : base(sourceCS, targetCS, null, null, createInverse, parameters)
    {
      validataSourceAndTarget(sourceCS, targetCS);

      if (Parameters.Count < 1)
        throw new ArgumentException("XYQuadrantTransform requires at least 1 paremater");
      
      Metadata = new Metadata("X, Y Quadrant transform", "none", -1, "", "", "provides transformation between exactly same Coordinate systems with different x,y axis order and / or direction");

//      UpdateTransform();
      
    }




    public XYQuadrantTransform(CoordinateSystem sourceCS,
                                   CoordinateSystem targetCS,
                                   params XParam[] parameters)
      : this(sourceCS, targetCS, false, parameters) { }





    public XYQuadrantTransform(CoordinateSystem sourceCS,
                               CoordinateSystem targetCS,
                               bool createInverse,
                               params XParam[] parameters)
      : this(sourceCS, targetCS, createInverse, new TransformParameters(parameters)) 
    { }


    #endregion constructors





    [DataMember(Name="SRC")]
    public override CoordinateSystem Source
    {
      get
      {
        return base.Source;
      }
      set
      {
        if (Target != null && value != null)
          validataSourceAndTarget(value, Target);

        base.Source = value;
      }
    }




    [DataMember(Name="TARGET")]
    public override CoordinateSystem Target
    {
      get
      {
        return base.Target;
      }
      set
      {
        if (value != null && Source != null)
          validataSourceAndTarget(Source, value);

        base.Target = value;
      }
    }





    protected override void transform(ICoordinate c)
    {
      double tmp;

      if (_SwapXY)
      {
        tmp = c.Y;
        c.Y = c.X;
        c.X = tmp;
      }

      if (_FlipX)
        c.X *= -1;

      if (_FlipY)
        c.Y *= -1;
    }




    //public override CoordStruct Transform(CoordStruct c)
    //{
    //  // pointD is structure = value type = on stack
    //  // no necessary to create new CoordStruct

    //  double tmp;

    //  if (_SwapXY)
    //  {
    //    tmp = c.X;
    //    c.Y *= c.X;
    //    c.X *= tmp;
    //  }

    //  if (_FlipX)
    //    c.X *= -1;

    //  if (_FlipY)
    //    c.Y *= -1;

    //  return c;
    //}





    private void validataSourceAndTarget(CoordinateSystem sourceCS, CoordinateSystem targetCS)
    {
      if (sourceCS == null || targetCS == null)
        return;

      if (!(sourceCS is ProjectedCoordinateSystem && targetCS is ProjectedCoordinateSystem))
        throw new ArgumentException("invalid operation, source and target coordinate systems must be " + typeof(ProjectedCoordinateSystem).ToString());

      if (sourceCS.GetType() != targetCS.GetType())
        throw new ArgumentException("invalid operation, source and target Coordinate systems are not of same type");

      if (!sourceCS.EqualParams(targetCS, CsCompareType.SkipProjectionAxesOrientation))
        throw new ArgumentException("invalid operation, source and target Coordinate systems are not exactly same");

    }





    protected override bool parametersValid()
    {
      return (Parameters.ContainsKey("SwapXY") || Parameters.ContainsKey("FlipX") || Parameters.ContainsKey("FlipY"));
    }





    /// <summary>
    /// must override here, spheric conformal doesnt need parameter distance units and angular param units
    /// </summary>
    /// <returns></returns>
    protected override bool graphCompleted()
    {
      return parametersValid();
    }





    protected override void updateTransform()
    {

      if (!(Parameters.ContainsKey("SwapXY") || Parameters.ContainsKey("FlipX") || Parameters.ContainsKey("FlipY")))
        throw new ArgumentException(this.GetType().ToString() + " must contain at least one of following parameters" +
                  "SwapXY, FlipX, FlipY. If any attribute is present then value is true");

      if (Parameters.ContainsKey("SwapXY"))
        _SwapXY = true;

      if (Parameters.ContainsKey("FlipX"))
        _FlipX = true;

      if (Parameters.ContainsKey("FlipY"))
        _FlipY = true;

      // it is autoinversible, no chenges needed ;-)
      //if (isInverse)
      //{
      //  _SwapXY = !_SwapXY;
      //  _FlipX = !_FlipX;
      //  _FlipY = !_FlipX; 
      //}


    }




    /// <summary>
    /// creates inverse transfor to current <c>XYQuadrantTransform</c>
    /// </summary>
    /// <returns>inverse transform</returns>
    public override SRSTransform Inverse()
    {
      return new XYQuadrantTransform(Source, Target, !IsInverse, Parameters);
    }




    /// <summary>
    /// returns transform dimension = Dimension.dim2D
    /// </summary>
    public override Dimension TransformDimension
    {
      get { return Dimension.dim2D; }
    }




    /// <summary>
    /// create transform from pair of coordinate systems
    /// </summary>
    /// <param name="sourceCS">source coordinate system</param>
    /// <param name="targetCS">target coordinate system</param>
    /// <returns>transform when both coordinate systems are valid, otherwise null</returns>
    public static XYQuadrantTransform Create(ProjectedCoordinateSystem sourceCS, ProjectedCoordinateSystem targetCS)
    {

      Axis cs1X = getAxis(sourceCS.Axes, "X");
      Axis cs1Y = getAxis(sourceCS.Axes, "Y");
      Axis cs2X = getAxis(targetCS.Axes, "X");
      Axis cs2Y = getAxis(targetCS.Axes, "Y");

      if (cs1X != null && cs1Y != null && cs2X != null && cs2Y != null)
      {

        XYQuadrantTransform result = new XYQuadrantTransform();
        result.Source = sourceCS;
        result.Target = targetCS;

        //double xtransEdge = 0, ytransEdge = 0;
        double xScale = 0, yScale = 0;
        //bool axisFlip = false;

        // same X
        if ((cs1X.Orientation == Axis.East && cs2X.Orientation == Axis.East) ||
            (cs1X.Orientation == Axis.West && cs2X.Orientation == Axis.West))
        {
          xScale = 1.0;
        }
        // oposite x
        else if ((cs1X.Orientation == Axis.East && cs2X.Orientation == Axis.West) ||
                 (cs1X.Orientation == Axis.West && cs2X.Orientation == Axis.East))
        {
          xScale = -1.0;
        }

        // same Y
        if ((cs1Y.Orientation == Axis.North && cs2Y.Orientation == Axis.North) ||
            (cs1Y.Orientation == Axis.South && cs2Y.Orientation == Axis.South))
        {
          yScale = 1.0;
        }
        // oposite y
        else if ((cs1Y.Orientation == Axis.South && cs2Y.Orientation == Axis.North) ||
                 (cs1Y.Orientation == Axis.North && cs2Y.Orientation == Axis.South))
        {
          yScale = -1.0;
        }

        if (xScale != 0 && yScale != 0)
        {
          if (xScale == -1)
            result.Parameters.Add("FlipX", 0);
          if (yScale == -1)
            result.Parameters.Add("FlipY", 0);

          return result;
        }

        // if not resolved,  try all other possibilities eg x.south, y.west,....
        if (xScale == 0 || yScale == 0)
        {
          // looks like weird cs def...
          if (cs1X.Orientation == Axis.South && cs1Y.Orientation == Axis.West &&
              cs2X.Orientation == Axis.East && cs2Y.Orientation == Axis.North)
          {
            result.Parameters.Add("SwapXY",0);
            result.Parameters.Add("FlipX", 0);
            result.Parameters.Add("FlipY", 0);

            return result;
          }
          // same but reverse
          else if (cs1X.Orientation == Axis.East && cs1Y.Orientation == Axis.North &&
                   cs2X.Orientation == Axis.South && cs2Y.Orientation == Axis.West)
          {
            result.Parameters.Add("SwapXY", 0);
            result.Parameters.Add("FlipX", 0);
            result.Parameters.Add("FlipY", 0);
            result.UpdateTransform();

            return result;
          }
          // todo: resolve more
        }

      }

      return null;
      
    }





    //public static XYQuadrantTransform Create(ProjectedCoordinateSystem sourceCS, AxisList targetAxisDef)
    //{
    //  // save cs to xaml
    //  string tmp = System.Windows.Markup.XamlWriter.Save(sourceCS);
    //  // load as new, instead of clone....
    //  ProjectedCoordinateSystem tmpCs = (ProjectedCoordinateSystem)System.Windows.Markup.XamlReader.Parse(tmp);
    //  // change its axis def...
    //  tmpCs.Axes = targetAxisDef;
    //  return Create(sourceCS, tmpCs);
    //}





    protected static Axis getAxis(AxisList AxList, string name)
    {
      foreach (Axis a in AxList)
        if (a.Name.ToLower() == name.ToLower())
        {
          if (!(a.Orientation == Axis.East ||
                a.Orientation == Axis.West ||
                a.Orientation == Axis.North ||
                a.Orientation == Axis.South))
            throw new ArgumentException("invalid axes orientation, must be east, west, north, south");

          return a;
        }
      return null;
    }


  }
}
